package edu.udo.sopra10.chaturaji.gui;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;

import javax.swing.BorderFactory;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

import edu.udo.sopra10.chaturaji.controllers.GameController;
import edu.udo.sopra10.chaturaji.entities.Player;
import edu.udo.sopra10.chaturaji.entities.Player.Type;
import edu.udo.sopra10.chaturaji.entities.coordinates.Position;
import edu.udo.sopra10.chaturaji.entities.pieces.Piece.Actions;
import edu.udo.sopra10.chaturaji.entities.transfer.MoveRequest;
import edu.udo.sopra10.chaturaji.entities.transfer.Piece;
import edu.udo.sopra10.chaturaji.events.BoardResetEvent;
import edu.udo.sopra10.chaturaji.events.BoardResetEventListener;
import edu.udo.sopra10.chaturaji.events.GameOverEvent;
import edu.udo.sopra10.chaturaji.events.GameOverEventListener;
import edu.udo.sopra10.chaturaji.events.MoveEvent;
import edu.udo.sopra10.chaturaji.events.MoveEventListener;
import edu.udo.sopra10.chaturaji.events.MoveRequestEvent;
import edu.udo.sopra10.chaturaji.events.MoveRequestEventListener;
import edu.udo.sopra10.chaturaji.events.TurnRequestEvent;
import edu.udo.sopra10.chaturaji.events.TurnRequestEventListener;
import edu.udo.sopra10.chaturaji.exceptions.BadStateException;
import edu.udo.sopra10.chaturaji.exceptions.ChaturajiException;
import edu.udo.sopra10.chaturaji.exceptions.IllegalMoveException;
import edu.udo.sopra10.chaturaji.exceptions.NoPieceOwnershipException;
import edu.udo.sopra10.chaturaji.gui.util.CenteredThreeColumnLayout;
import edu.udo.sopra10.chaturaji.gui.util.ChaturajiGamePanelLayout;
import edu.udo.sopra10.chaturaji.gui.util.GuiLock;
import edu.udo.sopra10.chaturaji.gui.util.SimpleGridBagConstraints;

public class GamePanel extends JPanel implements MouseListener, MoveEventListener, TurnRequestEventListener,
		MoveRequestEventListener, BoardResetEventListener, GameOverEventListener
{
	private static final long serialVersionUID = -2603288052036565794L;
	private static final int DELAY_TURN = 1000;
	private static final int DELAY_AI_MOVE = 500;
	
	private static HashMap<Player.Type, String> PLAYER_TYPES = new HashMap<Type, String>( 4 );
	
	static
	{
		PLAYER_TYPES.put( Type.HUMAN, "" );
		PLAYER_TYPES.put( Type.EMAIL, "E-Mail Spieler" );
		PLAYER_TYPES.put( Type.AI_EASY, "Einfacher Computergegner" );
		PLAYER_TYPES.put( Type.AI_MEDIUM, "Mittlerer Computergegner" );
		PLAYER_TYPES.put( Type.AI_HARD, "Schwerer Computergegner" );
	}
	
	private GameFrame gameFrame;
	private OverlayPanel overlay;
	private SquarePanel[][] squares;
	private PlayerStatusPanel[] playerPanels;
	private JPanel boardPanel;
	
	private HashSet<Piece> selectableSquares;
	private Position selectedSquare;
	private byte currentPlayer;
	private ArrayList<Byte> availableRolls;
	private MoveRequest hintedMove;
	
	public static GuiLock guiLock = new GuiLock();
	
	public GamePanel ( GameFrame gameFrame )
	{
		super( new CenteredThreeColumnLayout() );
		
		this.gameFrame = gameFrame;
		this.overlay = (OverlayPanel) this.gameFrame.getGlassPane();
		
		/* player status panels */
		playerPanels = new PlayerStatusPanel[ 4 ];
		playerPanels[0] = new PlayerStatusPanel( "Player 1", "Player type", (byte) 0 );
		playerPanels[1] = new PlayerStatusPanel( "Player 2", "Player type", (byte) 1 );
		playerPanels[2] = new PlayerStatusPanel( "Player 3", "Player type", (byte) 2 );
		playerPanels[3] = new PlayerStatusPanel( "Player 4", "Player type", (byte) 3 );
		
		this.add( ChaturajiGamePanelLayout.LEFT, playerPanels[0] );
		this.add( ChaturajiGamePanelLayout.RIGHT, playerPanels[1] );
		this.add( ChaturajiGamePanelLayout.RIGHT, playerPanels[2] );
		this.add( ChaturajiGamePanelLayout.LEFT, playerPanels[3] );
		
		/* board panel */
		boardPanel = new JPanel( new GridLayout( 8, 8, 1, 1 ) );
		boardPanel.setBackground( Color.DARK_GRAY );
		boardPanel.setBorder( BorderFactory.createMatteBorder( 2, 2, 2, 2, Color.DARK_GRAY ) );
		boardPanel.setMinimumSize( new Dimension( 80, 80 ) );
		
		SimpleGridBagConstraints c = new SimpleGridBagConstraints();
		
		/*-
		c.weightx = 0;
		c.weighty = 0;
		c.fill = GridBagConstraints.BOTH;
		
		boardPanel.add( new JLabel( "A" ), c.setGrid( 1, 0 ) );
		boardPanel.add( new JLabel( "B" ), c.setGrid( 2, 0 ) );
		boardPanel.add( new JLabel( "C" ), c.setGrid( 3, 0 ) );
		boardPanel.add( new JLabel( "D" ), c.setGrid( 4, 0 ) );
		boardPanel.add( new JLabel( "E" ), c.setGrid( 5, 0 ) );
		boardPanel.add( new JLabel( "F" ), c.setGrid( 6, 0 ) );
		boardPanel.add( new JLabel( "G" ), c.setGrid( 7, 0 ) );
		boardPanel.add( new JLabel( "H" ), c.setGrid( 8, 0 ) );
		boardPanel.add( new JLabel( "1" ), c.setGrid( 0, 1 ) );
		boardPanel.add( new JLabel( "2" ), c.setGrid( 0, 2 ) );
		boardPanel.add( new JLabel( "3" ), c.setGrid( 0, 3 ) );
		boardPanel.add( new JLabel( "4" ), c.setGrid( 0, 4 ) );
		boardPanel.add( new JLabel( "5" ), c.setGrid( 0, 5 ) );
		boardPanel.add( new JLabel( "6" ), c.setGrid( 0, 6 ) );
		boardPanel.add( new JLabel( "7" ), c.setGrid( 0, 7 ) );
		boardPanel.add( new JLabel( "8" ), c.setGrid( 0, 8 ) );
		
		
		c.weightx = 1;
		c.weighty = 1;
		c.setInsets( 0, 1, 1, 0 );
		 */

		// initialize squares
		SquarePanel square;
		squares = new SquarePanel[ 8 ][ 8 ];
		
		for ( byte j = 0; j < 8; j++ )
		{
			for ( byte i = 0; i < 8; i++ )
			{
				square = new SquarePanel();
				square.setPosition( new Position( i, j ) );
				square.setBackground( Color.WHITE );
				square.addMouseListener( this );
				
				boardPanel.add( square, c.setGrid( i + 1, j + 1 ) );
				squares[i][j] = square;
			}
		}
		
		this.add( ChaturajiGamePanelLayout.CENTER, boardPanel );
	}
	
	/**
	 * Pass the current turn.
	 * 
	 */
	public void passTurn ()
	{
		if ( selectableSquares == null )
			return;
		
		try
		{
			GameController.pass();
		}
		catch ( BadStateException e )
		{
			return;
		}
	}
	
	/**
	 * Shows a hint for the current player.
	 * 
	 */
	public void showHint ()
	{
		hintedMove = GameController.getHint( availableRolls );
		selectedSquare = null;
		actualizeSquareHinting();
	}
	
	@Override
	public void onMoveEvent ( final MoveEvent event )
	{
		selectableSquares = null;
		
		SwingUtilities.invokeLater( new Runnable()
			{
				@Override
				public void run ()
				{
					// update player status
					PlayerStatusPanel playerPanel = playerPanels[event.getPlayerId()];
					playerPanel.setScore( event.getFullScore() );
					
					// update captured pieces
					for ( Piece piece : event.getCapturedPieces() )
					{
						playerPanel.addPiece( new SquarePanel( piece.getPieceType(), piece.getPlayerId() ) );
						getSquareAt( piece.getPosition() ).resetPiece();
					}
					
					// update promoted pawns
					for ( Piece piece : event.getPromotedPawns() )
					{
						getSquareAt( piece.getPosition() ).setPiece( piece.getPieceType(), piece.getPlayerId() );
					}
					
					// updated moved piece
					getSquareAt( event.getStartPosition() ).resetPiece();
					getSquareAt( event.getEndPosition() ).setPiece( event.getMovedPiece().getPieceType(),
							event.getPlayerId() );
				}
			} );
		
		// TODO: Strike the used die
		event.getDiceRoll();
	}
	
	@Override
	public void onTurnRequestEvent ( final TurnRequestEvent event )
	{
		currentPlayer = event.getPlayerId();
		
		selectableSquares = null;
		selectedSquare = null;
		hintedMove = null;
		
		// update statistics
		gameFrame.setCurrentTurn( event.getTurnNumber(), currentPlayer );
		
		// show overlay
		guiLock.delay( DELAY_TURN );
		SwingUtilities.invokeLater( new Runnable()
			{
				@Override
				public void run ()
				{
					switch ( event.getPlayerType() )
					{
						case HUMAN:
							overlay.showHumanTurnOverlay( playerPanels[currentPlayer] );
							break;
						
						case EMAIL:
							overlay.showEmailTurnOverlay( playerPanels[currentPlayer] );
							break;
						
						default:
							overlay.showAITurnOverlay( playerPanels[currentPlayer] );
							break;
					}
					
				}
			} );
	}
	
	@Override
	public void onMoveRequestEvent ( final MoveRequestEvent event )
	{
		currentPlayer = event.getPlayerId();
		availableRolls = event.getUnassignedRolls();
		selectedSquare = null;
		hintedMove = null;
		
		switch ( event.getPlayerType() )
		{
			case HUMAN:
				if ( overlay != null && !event.isFirstMoveMade() )
				{
					final ArrayList<Byte> rolls = event.getUnassignedRolls();
					overlay.rollTo( rolls.get( 0 ), rolls.get( 1 ) );
					guiLock.lockAndWait();
				}
				
				selectableSquares = event.getMoveablePieces();
				actualizeSquareHinting();
				break;
			
			case EMAIL:
				if ( !event.isFirstMoveMade() )
				{
					// make first move
					// TODO: GameController.
				}
				else
				{
					// make second move
					// TODO: GameController.
				}
				break;
			
			default:
				if ( overlay != null && !event.isFirstMoveMade() )
				{
					final ArrayList<Byte> rolls = event.getUnassignedRolls();
					overlay.rollTo( rolls.get( 0 ), rolls.get( 1 ) );
					guiLock.lockAndWait();
					guiLock.delay( DELAY_TURN );
				}
				else
					guiLock.delay( DELAY_AI_MOVE );
				
				break;
		}
	}
	
	@Override
	public void onBoardResetEvent ( final BoardResetEvent event )
	{
		selectableSquares = null;
		selectedSquare = null;
		hintedMove = null;
		
		SwingUtilities.invokeLater( new Runnable()
			{
				@Override
				public void run ()
				{
					Piece[][] board = event.getBoard();
					
					for ( byte j = 0; j < 8; j++ )
					{
						for ( byte i = 0; i < 8; i++ )
						{
							if ( board[i][j] == null )
								squares[i][j].resetPiece();
							else
								squares[i][j].setPiece( board[i][j].getPieceType(), board[i][j].getPlayerId() );
							
							// reset square
							squares[i][j].setSquare( "" );
						}
					}
					
					for ( byte i = 0; i < 4; i++ )
					{
						PlayerStatusPanel panel = playerPanels[i];
						panel.updateLabels( event.getPlayerName( i ), PLAYER_TYPES.get( event.getPlayerType( i ) ) );
						panel.setScore( event.getScore( i ) );
						panel.removeAllPieces();
						
						for ( Piece piece : event.getCaptured( i ) )
							panel.addPiece( new SquarePanel( piece.getPieceType(), piece.getPlayerId() ) );
					}
					
					guiLock.unlock();
				}
			} );
		
		guiLock.lockAndWait();
	}
	
	@Override
	public void onGameOverEvent ( GameOverEvent event )
	{
		JOptionPane.showMessageDialog( this, "Das Spielziel ist erreicht, das Spiel ist beendet.", "Ziel erreicht",
				JOptionPane.INFORMATION_MESSAGE );
	}
	
	@Override
	public void mousePressed ( final MouseEvent event )
	{
		if ( selectableSquares != null )
		{
			final SquarePanel source = (SquarePanel) event.getSource();
			
			if ( selectedSquare == null )
			{
				if ( source.getPlayerId() != currentPlayer )
					return;
				
				if ( GameController.hasOwnedPiece( source.getPosition() ) )
					selectedSquare = source.getPosition();
				
				actualizeSquareHinting();
			}
			else
			{
				if ( source.getPosition().equals( selectedSquare ) )
				{
					selectedSquare = null;
					actualizeSquareHinting();
					return;
				}
				
				try
				{
					GameController.move( selectedSquare, source.getPosition() );
					selectedSquare = null;
					actualizeSquareHinting();
				}
				catch ( BadStateException e )
				{
					return;
				}
				catch ( IllegalMoveException e )
				{
					return;
				}
				catch ( ChaturajiException e )
				{
					selectedSquare = null;
					actualizeSquareHinting();
				}
			}
		}
	}
	
	@Override
	public void mouseClicked ( final MouseEvent event )
	{
	}
	
	@Override
	public void mouseEntered ( final MouseEvent event )
	{
	}
	
	@Override
	public void mouseExited ( final MouseEvent event )
	{
	}
	
	@Override
	public void mouseReleased ( final MouseEvent event )
	{
	}
	
	
	/**
	 * Resets all square panels and recalculates any possible hints, based on
	 * the current phase.
	 * 
	 */
	private void actualizeSquareHinting ()
	{
		resetSquares();
		
		if ( selectedSquare != null )
		{
			// show possible targets for the selected piece
			try
			{
				Actions actions = GameController.getPossibleActions( selectedSquare );
				
				// display possible moves
				for ( Position position : actions.getPossibleMoves() )
					getSquareAt( position ).setSquare( SquarePanel.ACTION_MOVE, currentPlayer );
				
				// display possible captures
				for ( Position position : actions.getPossibleCaptures() )
					getSquareAt( position ).setSquare( SquarePanel.ACTION_CAPTURE, currentPlayer );
				
				// display possible pawn promotions
				for ( Position position : actions.getPossiblePawnPromotions() )
					getSquareAt( position ).setSquare( SquarePanel.ACTION_PROMOTE, currentPlayer );
				
				// display possible boat triumph
				Position position = actions.getPossibleBoatTriumph();
				if ( position != null )
					getSquareAt( position ).setSquare( SquarePanel.ACTION_TRIUMPH, currentPlayer );
			}
			catch ( NoPieceOwnershipException e )
			{
				return;
			}
		}
		else if ( hintedMove != null )
		{
			getSquareAt( hintedMove.start ).setSquare( SquarePanel.ACTION_HINT, "" );
			getSquareAt( hintedMove.end ).setSquare( SquarePanel.ACTION_HINT, "" );
		}
		else if ( selectableSquares != null )
		{
			// show selectable pieces
			for ( Piece piece : selectableSquares )
				getSquareAt( piece.getPosition() ).setSquare( SquarePanel.ACTION_SELECT, (byte) currentPlayer );
		}
	}
	
	/**
	 * Resets all displayed squares.
	 * 
	 */
	private void resetSquares ()
	{
		for ( byte j = 0; j < 8; j++ )
		{
			for ( byte i = 0; i < 8; i++ )
			{
				squares[i][j].setSquare( "" );
			}
		}
	}
	
	/**
	 * Returns the {@link SquarePanel} at the given position.
	 * 
	 * @param position the position of the square.
	 * @return the referenced panel.
	 */
	private SquarePanel getSquareAt ( Position position )
	{
		return squares[position.getX()][position.getY()];
	}
}